#pragma once

#include <string>
#include <ctime>
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <pthread.h>
#include <fstream>
#include "lockguard.hpp"
using namespace std;

// 日志
// 日志有等级、时间、文件名、行号、日志的内容
#define LOG(level, format, ...)                                       \
    do                                                                \
    {                                                                 \
        logMessage(__FILE__, __LINE__, #level, format, ##__VA_ARGS__); \
    } while (0)
// __VA_ARGS__, c99的新特性定义宏的时候支持可变参数
// ##预处理符，表示函数logMessage可以有可变参数，也可以没有可变参数，没有##，必须有可变参数
// 将函数定义成宏，可以传参的时候不用传宏__FILE__、__LINE__这两个参数
// 用do while(0)语句，因为1、do while是一个不完整的语句，用宏的时候可以加分号，更像函数调用。2、将当代码多的时候，将其写在代码块有更好的安全性

enum Level // 日志等级
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

string levelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "debug";
    case INFO:
        return "info";
    case WARNING:
        return "warning";
    case ERROR:
        return "error";
    case FATAL:
        return "fatal";
    default:
        return "unknown";
    };
}

string timeToString()
{
    time_t curtime = time(nullptr);       // 获取时间戳
    struct tm *ptm = localtime(&curtime); // 将时间戳转换成时间结构体
    char buff[1024];
    sprintf(buff, "%d-%d-%d %d:%d:%d", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    return buff;
}

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义一把锁，保护显示器资源
int gissave = false;
//定义两个使能按钮
#define EnableScreen()   \
    do                   \
    {                    \
        gissave = false; \
    } while (0)

#define EnableFile()    \
    do                  \
    {                   \
        gissave = true; \
    } while (0)

void toSaveFile(string& filename, string& msg)
{
    ofstream out(filename, ios::app);
    if(!out.is_open())
    {
        return;
    }
    out << msg;
    out.close();
}

void logMessage(string filename, int line, string level, const char *format, ...) // 函数形参压栈是从右往左
{
    //string curlevel = levelToString(level);
    string curtime = timeToString();

    va_list al;           // 定义一个可变参数列表指针，va_list本质是void*
    va_start(al, format); // 初始化指针，让其指向可变参数
    char buf[1024];
    vsprintf(buf, format, al); // 将形参以format的格式写入到buf中
    va_end(al);                // 清空指针，类似于nullptr

    // string msg = "[" + curtime + "]" + "[" + curlevel + "]" + "[" + filename + "]" + "[" + to_string(line) + "]"
    //      + buf;
    
    string msg = "[" + curtime + "]" + "[" + level + "]" + "[" + filename + "]" + "[" + to_string(line) + "]" + buf;
    
    LockGuard lock(mutex);
    if(gissave)
    {
        //保存到文件
        string logfilename = "log.txt";
        toSaveFile(logfilename, msg);
    }
    else
    {
        //LockGuard lock(mutex);
        //输出到显示器
        cout << msg;
    }
}

// 可变参数是如何被解释的(测试默认可变参数都是int类型)
void testVargs(int num, ...) // 有可变参数的函数，至少有一个具体的参数，并且从左往有排列
{
    va_list al;        // 定义一个指针
    va_start(al, num); // al是指向可变参数的指针，num是具体参数的最右边那个，方便al跳过sizeof(num)个字节指向可变参数
    while (num--)
    {
        int data = va_arg(al, int); // va_arg可以根据int类型来获取字节数，从而获得形参
        cout << data << " ";
    }
    cout << endl;
    va_end(al); // 清空指针

    // 注：printf中第一个参数是格式化字符串，怎么解释可变参数
    // 格式化字符串"%c%d%f%lld"
    // 1、%表示了有多少个参数，%c表示了类型，从而就可以获得
    // vsprint系列函数可以不用向上面这么麻烦，直接将可变参数按格式化字符串输出到数组中，类似于sprintf
}